//
//  vdShader.m
//  Void Dead
//
//  Created by Sidney Just on 23.11.09.
//
//  Copyright © 2009 by Sidney Just
//  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
//  documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
//  the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
//  and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
//  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
//  PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
//  FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

#import "vdShader.h"
#import "vdKernel.h"


@implementation vdShader
@synthesize lastError, vertices, texCoords, shader, texture, color, positionsPerVertex, matrix;
@synthesize isPostProcessingShader;

#pragma mark -
#pragma mark Binding/Getting

- (void)bindAttributeLocations {
	if(delegate)
	{
		if([delegate respondsToSelector:@selector(bindAttributeLocations:)])
			[delegate bindAttributeLocations:self];
	}
}

- (void)getUniformLocations {
	if(delegate)
	{
		if([delegate respondsToSelector:@selector(getUniformLocations:)])
			[delegate getUniformLocations:self];
	}
}

- (void)runShader {
	if(delegate)
	{
		if([delegate respondsToSelector:@selector(runShader:)])
			[delegate runShader:self];
	}
}

#pragma mark -
#pragma mark Errors

- (void)checkForErrors {
	if(lastError != vdNoError)
	{
		vdErrorMessage message;
		message.event = vdEngineError;
		message.vdError = lastError;
		
		[[vdEventHandler sharedEventHandler] fireEvent:vdErrorEvent sender:self data:&message];
	}
	lastError = vdNoError;
}

#pragma mark -
#pragma mark Loading

#if defined(USE_OS_30) || defined(USE_OS_32) || defined(USE_OS_40) || USE_OS_32 || USE_OS_40
- (BOOL)compileShaderFromSource:(GLuint *)_shader type:(GLenum)type source:(NSString *)src {
	GLint status;
	const GLchar *source;
	
	source = (GLchar *)[src UTF8String];
	
	if (!source)
	{
		lastError = vdShaderFailedToLoadShader;
		[self checkForErrors];
		return NO;
	}
	
    *_shader = glCreateShader(type);
    glShaderSource(*_shader, 1, &source, NULL);
    glCompileShader(*_shader);
    
    glGetShaderiv(*_shader, GL_COMPILE_STATUS, &status);
    if (status == 0)
	{
		glDeleteShader(*_shader);
		return NO;
	}
	
	return YES;
}

- (BOOL)linkProgram:(GLuint)prog {
	GLint status;
	
	glLinkProgram(prog);
    
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if (status == 0)
		return NO;
	
	return YES;
}

- (BOOL)validateProgram:(GLuint)prog {
	GLint logLength, status;
	
	glValidateProgram(prog);
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0)
    {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        free(log);
    }
    
    glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
    if (status == 0)
		return NO;
	
	return YES;
}

- (BOOL)processVertexShader:(NSString *)vertexShader andFragmentShader:(NSString *)fragmentShader {
	GLuint vertShader, fragShader;
    
    shader = glCreateProgram();
	
	if (![self compileShaderFromSource:&vertShader type:GL_VERTEX_SHADER source:vertexShader])
	{
		lastError = vdShaderFailedToCompileVertexShader;
		[self checkForErrors];
		return NO;
	}
	
	
	if (![self compileShaderFromSource:&fragShader type:GL_FRAGMENT_SHADER source:fragmentShader])
	{
		lastError = vdShaderFailedToCompileFragmentShader;
		[self checkForErrors];
		return NO;
	}
    
    glAttachShader(shader, vertShader);
    glAttachShader(shader, fragShader);
	
    // Bind Attributes
	[self bindAttributeLocations];
    
	if (![self linkProgram:shader])
	{
		lastError = vdShaderFailedToLinkShader;
		[self checkForErrors];
		return NO;
	}
    
    // get uniforms
    [self getUniformLocations];
	
    if(vertShader)
		glDeleteShader(vertShader);
    if(fragShader)
		glDeleteShader(fragShader);

	vdLog(@"Loaded vertex and fragment shader data into %@", self);
	return YES;
}

#endif

- (BOOL)loadShader:(NSString *)shdFile {
#if defined(USE_OS_30) || defined(USE_OS_32) || defined(USE_OS_40) || USE_OS_32 || USE_OS_40
	if([vdKernel sharedKernel].rAPI == OpenGLESAPI1)
	{
		lastError = vdShaderUnsupported;
		return NO;
	}

	NSString *vertShaderPathname = [[NSBundle mainBundle] pathForResource:shdFile ofType:@"vsh"];
	NSString *fragShaderPathname = [[NSBundle mainBundle] pathForResource:shdFile ofType:@"fsh"];


	
	return [self processVertexShader:[NSString stringWithContentsOfFile:vertShaderPathname encoding:NSUTF8StringEncoding error:nil] 
				  andFragmentShader:[NSString stringWithContentsOfFile:fragShaderPathname encoding:NSUTF8StringEncoding error:nil]];

#else
	lastError = vdShaderUnsupported;
	[self checkForErrors];
	return NO;
#endif
}

- (BOOL)loadVertexShader:(NSString *)vShader andFragmentShader:(NSString *)fShader fromHDD:(BOOL)hdd {
#if defined(USE_OS_30) || defined(USE_OS_32) || defined(USE_OS_40) || USE_OS_32 || USE_OS_40
	if([vdKernel sharedKernel].rAPI == OpenGLESAPI1)
	{
		lastError = vdShaderUnsupported;
		return NO;
	}
	
	if(!hdd)
	{
		return [self processVertexShader:vShader andFragmentShader:fShader];
	}
	else 
	{
		NSString *vertShaderPathname = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], vShader];
		NSString *fragShaderPathname = [NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], fShader];
		
		return [self processVertexShader:[NSString stringWithContentsOfFile:vertShaderPathname encoding:NSUTF8StringEncoding error:nil] 
					 andFragmentShader:[NSString stringWithContentsOfFile:fragShaderPathname encoding:NSUTF8StringEncoding error:nil]];
	}

#else
	lastError = vdShaderUnsupported;
	[self checkForErrors];
	return NO;
#endif
}

- (void)setDelegate:(id)del {
	delegate = del;
}

- (id)init {
	if(self = [super init])
	{
		lastError = vdNoError;
		shader = 0;
		
		delegate = NULL;
		
		vdLog(@"Created shader %@", self);
	}
	return self;
}

- (void)dealloc {
#if defined(USE_OS_30) || defined(USE_OS_32) || defined(USE_OS_40) || USE_OS_32 || USE_OS_40
	if(shader != 0)
		glDeleteShader(shader);
#endif
	vdLog(@"Destroyed shader %@", self);
	
	[super dealloc];
}

@end
